import { Injectable, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable, of, throwError, ReplaySubject } from 'rxjs';
import { tap, catchError, switchMap } from 'rxjs/operators';
import { Router } from '@angular/router';
import { Store } from '@ngrx/store';
import { TranslateService } from '@ngx-translate/core';
import { jwtDecode } from 'jwt-decode';
import { AppState } from '@core/cne.state';
import { LoginRequest, LoginResponse } from '@shared/models/login.models';
import { AuthUser } from '@shared/models/user.models';
import { AuthPayload } from './auth.models';
import { 
  actionAuthAuthenticated, 
  actionAuthUnauthenticated, 
  actionAuthLoadUser 
} from './auth.actions';
import { getCurrentAuthUser } from './auth.selectors';

/**
 * 认证服务
 * 
 * 核心功能：
 * - JWT Token 管理和验证
 * - 用户登录/登出
 * - Token 自动刷新（防并发）
 * - NgRx 状态同步
 * 
 * 设计模式：
 * - ReplaySubject：防并发 Token 刷新
 * - 状态分离：认证与用户加载独立管理
 */
@Injectable({
  providedIn: 'root'
})
export class AuthService {

  private readonly http = inject(HttpClient);
  private readonly router = inject(Router);
  private readonly store = inject(Store<AppState>);
  private readonly translate = inject(TranslateService);

  private refreshTokenSubject: ReplaySubject<LoginResponse> | null = null;

  // 重定向 URL - 用于登录后跳转到原始页面
  redirectUrl: string | undefined;

  /**
   * 获取 JWT Token
   * 
   * @returns string | null JWT Token 字符串，如果没有则返回 null
   */
  public getJwtToken(): string | null {
    return localStorage.getItem('jwt_token');
  }

  /**
   * 检查 JWT Token 是否有效
   * 
   * @returns boolean 如果 Token 有效（未过期）则返回 true，否则返回 false
   */
  public isJwtTokenValid(): boolean {
    const token = this.getJwtToken();
    if (!token) {
      return false;
    }

    // 检查 Token 格式（应该有3个部分）
    const parts = token.split('.');
    if (parts.length !== 3) {
      return false;
    }

    // 检查过期时间
    const isValidByTime = this.isTokenValid('jwt_token');
    if (!isValidByTime) {
      return false;
    }

    // 尝试解析 Token 以验证格式
    try {
      jwtDecode(token);
      return true;
    } catch (e) {
      return false;
    }
  }

  /**
   * 检查是否正在刷新 Token
   * 
   * 用途：防止多个请求同时触发 Token 刷新
   * 当返回 true 时，新的刷新请求会等待现有请求完成
   * 
   * @returns boolean 如果正在刷新 Token 则返回 true，否则返回 false
   */
  public refreshTokenPending(): boolean {
    return this.refreshTokenSubject !== null;
  }

  /**
   * 用户登录
   * 
   * 流程：
   * 1. 发送登录请求到后端
   * 2. 成功后存储 JWT Token 和 Refresh Token
   * 3. 从 Token 中解析用户信息
   * 4. 通知认证状态变化
   * 
   * @param loginRequest 登录请求参数（用户名和密码）
   * @returns Observable<LoginResponse> 登录响应流
   */
  public login(loginRequest: LoginRequest): Observable<LoginResponse> {
    return this.http.post<LoginResponse>('/api/auth/login', loginRequest).pipe(
      tap((response: LoginResponse) => {
        // 存储 Token
        this.setTokens(response.token, response.refreshToken);
        
        // 从 Token 解析用户信息并通知认证状态
        this.setUserFromJwtToken(response.token, response.refreshToken, true);
      })
    );
  }

  /**
   * 用户登出
   * 
   * 流程：
   * 1. 可选：保存当前 URL 用于登录后重定向
   * 2. 可选：发送登出请求到后端
   * 3. 清除本地存储的 Token
   * 4. 通知未认证状态
   * 
   * @param captureLastUrl 是否保存当前 URL 用于重定向
   * @param ignoreRequest 是否忽略后端登出请求（直接清除本地状态）
   */
  public logout(captureLastUrl: boolean = false, ignoreRequest = false): void {
    // 保存重定向 URL
    if (captureLastUrl) {
      this.redirectUrl = this.router.url;
    }

    if (!ignoreRequest) {
      // 发送登出请求到后端，失败也继续清除本地状态
      this.http.post('/api/auth/logout', null).pipe(
        catchError(() => of(null))
      ).subscribe(() => this.clearTokens());
    } else {
      // 直接清除本地状态
      this.clearTokens();
    }
  }

  /**
   * 获取用户信息
   * 这是一个需要认证的请求，会被拦截器添加 JWT Token
   * 
   * @returns Observable<any> 用户信息响应流
   */
  public getUserProfile(): Observable<any> {
    return this.http.get('/api/user/profile');
  }

  /**
   * 刷新 JWT Token：防止并发刷新，多个请求共享同一个刷新结果
   * 
   * @param loadUserElseStoreJwtToken 是否加载用户信息（true）还是仅存储 token（false）
   * @returns Observable<LoginResponse> 刷新结果流
   */
  public refreshJwtToken(loadUserElseStoreJwtToken: boolean = true): Observable<LoginResponse> {
    // 如果已经有刷新操作在进行，直接返回现有的 Observable
    let response: Observable<LoginResponse> = this.refreshTokenSubject!;
    
    if (this.refreshTokenSubject === null) {
      // 创建新的 ReplaySubject，缓存最后1个值，后续订阅者可以立即获得刷新结果
      this.refreshTokenSubject = new ReplaySubject<LoginResponse>(1);
      response = this.refreshTokenSubject;
      
      // 检查 refresh token 是否有效
      const refreshToken = localStorage.getItem('refresh_token');
      const refreshTokenValid = this.isTokenValid('refresh_token');
      
      // 清除旧的用户状态（不通知）
      this.setUserFromJwtToken(null, null, false);
      
      if (!refreshTokenValid) {
        // refresh token 无效，异步获取本地化错误信息
        this.translate.get('access.refresh-token-expired').subscribe(
          (translation) => {
            this.refreshTokenSubject!.error(new Error(translation));
            this.refreshTokenSubject = null;
          }
        );
      } else {
        // refresh token 有效，发起刷新请求
        const refreshTokenRequest = { refreshToken };
        const refreshObservable = this.http.post<LoginResponse>('/api/auth/token', refreshTokenRequest);
        
        refreshObservable.subscribe({
          next: (loginResponse: LoginResponse) => {
            if (loadUserElseStoreJwtToken) {
              // 完整用户设置流程：设置 token 并异步加载用户信息（不通知状态变化）
              this.setUserFromJwtToken(loginResponse.token, loginResponse.refreshToken, false);
            } else {
              // 仅存储 token：更新 token 并通知状态变化
              this.updateAndValidateTokens(loginResponse.token, loginResponse.refreshToken, true);
            }
            
            // 更新用户信息（如果需要）
            this.updatedAuthUserFromToken(loginResponse.token);
            
            // 通知所有等待的订阅者
            this.refreshTokenSubject!.next(loginResponse);
            this.refreshTokenSubject!.complete();
            this.refreshTokenSubject = null; // 重置状态，允许下次刷新
          },
          error: () => {
            // 刷新失败：清除 tokens 并通知错误
            this.clearTokens();
            // 使用本地化错误信息
            this.refreshTokenSubject!.error(new Error(this.translate.instant('access.refresh-token-failed')));
            this.refreshTokenSubject = null; // 重置状态
          }
        });
      }
    }
    
    // 返回 ReplaySubject，多个订阅者会共享同一个刷新过程
    return response;
  }

  /**
   * 验证并更新本地 Token，异步加载用户信息
   * 
   * @param jwtToken JWT Token（可为 null）
   * @param refreshToken 刷新 Token（可为 null）
   * @param notify 是否通知用户状态变化
   * @returns Observable<boolean> 认证结果流（true=认证成功，false=认证失败）
   */
  public setUserFromJwtToken(jwtToken: string | null, refreshToken: string | null, notify: boolean): Observable<boolean> {
    // 创建 ReplaySubject 用于返回认证结果，缓存最后1个值确保异步操作完成后订阅者能收到结果
    const authenticatedSubject = new ReplaySubject<boolean>(1);
    
    // 检查 JWT Token 是否存在
    if (!jwtToken) {
      // 没有 JWT Token 则清除所有本地认证数据
      this.clearTokens();
      
      if (notify) {
        this.notifyUnauthenticated(); // 通知未认证状态
      }
      
      // 返回认证失败结果，通知订阅者
      authenticatedSubject.next(false);
      authenticatedSubject.complete();
    } else {
      // 有 JWT Token
      // 更新并验证 tokens（包括 JWT Token 和 Refresh Token）
      this.updateAndValidateTokens(jwtToken, refreshToken, true);
      
      if (notify) {
        // 通知状态变化，开始加载用户信息
        this.notifyUserLoaded(false); // 标记开始加载用户
        
        // 异步加载用户信息
        this.loadUser(false).subscribe({
          next: (authPayload) => {
            // 用户加载成功：通知认证状态
            this.notifyAuthenticated(authPayload);
            this.notifyUserLoaded(true);
            
            // 返回认证成功结果，通知订阅者
            authenticatedSubject.next(true);
            authenticatedSubject.complete();
          },
          error: () => {
            // 用户加载失败：通知未认证状态
            this.notifyUnauthenticated();
            this.notifyUserLoaded(true);
            
            // 返回认证失败结果，通知订阅者
            authenticatedSubject.next(false);
            authenticatedSubject.complete();
          }
        });
      } else {
        // 不需要通知状态变化，仅异步加载用户信息
        this.loadUser(false).subscribe({
          next: () => {
            // 用户加载成功：返回认证成功结果，通知订阅者
            authenticatedSubject.next(true);
            authenticatedSubject.complete();
          },
          error: () => {
            // 用户加载失败：返回认证失败结果，通知订阅者
            authenticatedSubject.next(false);
            authenticatedSubject.complete();
          }
        });
      }
    }
    
    // 返回认证结果流
    return authenticatedSubject;
  }

  /**
   * 加载用户信息
   * 
   * 模拟异步加载用户信息的过程
   * 实际项目中这里会调用用户信息 API
   * 
   * @param notify 是否通知状态变化
   * @returns Observable<AuthPayload> 用户信息流
   */
  private loadUser(notify: boolean): Observable<AuthPayload> {
    // 模拟异步加载用户信息的过程
    return new Observable<AuthPayload>(observer => {
      setTimeout(() => {
        // 从 Token 中获取用户信息
        const user = this.getCurrentUser();
        
        if (!user) {
          // 模拟用户加载失败的情况
          observer.error(new Error('用户信息加载失败：Token 无效或已过期'));
        } else {
          // 模拟额外的用户信息加载（如权限、设置等）
          const enhancedUser: AuthUser = {
            ...user,
            // 可以在这里添加从后端 API 获取的额外信息
            // permissions: ['read', 'write', 'admin'],
            // settings: { theme: 'dark', language: 'zh-CN' }
          };
          
          const authPayload: AuthPayload = { authUser: enhancedUser };
          observer.next(authPayload);
          observer.complete();
        }
      }, 200); // 模拟网络延迟
    });
  }

  /**
   * 更新并验证 Token
   * 
   * 批量更新 JWT Token 和 Refresh Token
   * 支持 null 值，如果 Token 为 null 则跳过更新
   * 
   * @param jwtToken JWT Token（可为 null）
   * @param refreshToken 刷新 Token（可为 null）
   * @param notify 是否通知状态变化
   */
  public updateAndValidateTokens(jwtToken: string | null, refreshToken: string | null, notify: boolean): void {
    if (jwtToken) {
      this.updateAndValidateToken(jwtToken, 'jwt_token', notify);
    }
    if (refreshToken) {
      this.updateAndValidateToken(refreshToken, 'refresh_token', notify);
    }
  }

  /**
   * 更新并验证单个 Token
   * 
   * 使用 jwt-decode 库解析 JWT Token，验证其有效性
   * 按照原始实现逻辑：检查 iat 和 exp 字段，计算 TTL
   * 
   * @param token Token 字符串
   * @param prefix Token 前缀（用于 localStorage 键名）
   * @param notify 是否通知状态变化
   */
  public updateAndValidateToken(token: string, prefix: string, notify: boolean): void {
    let valid = false;
    
    // 检查是否为 JWT Token（有3个部分）
    const parts = token.split('.');
    if (parts.length === 3) {
      // JWT Token：解析并验证
      try {
        const tokenData = jwtDecode(token) as any;
        const issuedAt = tokenData?.iat;
        const expTime = tokenData?.exp;
        
        if (issuedAt && expTime) {
          const ttl = expTime - issuedAt;
          if (ttl > 0) {
            const clientExpiration = new Date().valueOf() + ttl * 1000;
            localStorage.setItem(prefix, token);
            localStorage.setItem(prefix + '_expiration', '' + clientExpiration);
            valid = true;
          }
        }
      } catch (error) {
        valid = false;
      }
    } else {
      // 非 JWT Token（如 Refresh Token）：直接存储
      localStorage.setItem(prefix, token);
      // Refresh Token 设置较长的过期时间（24小时）
      const refreshExpiration = new Date().valueOf() + (24 * 60 * 60 * 1000);
      localStorage.setItem(prefix + '_expiration', '' + refreshExpiration);
      valid = true;
    }
    
    if (!valid && notify) {
      this.notifyUnauthenticated();
    }
  }

  /**
   * 从 Token 更新用户信息
   * 
   * 预留方法，用于在 Token 刷新后更新用户信息
   * 可以根据需要实现额外的用户信息同步逻辑
   * 
   * @param token JWT Token
   */
  public updatedAuthUserFromToken(token: string): void {
    // 这个方法可以根据需要实现用户信息更新逻辑
    // 目前简化实现，实际项目中可能需要额外的用户信息同步
    // TODO
  }

  /**
   * 检查 Token 是否有效
   * 
   * 通过比较存储的过期时间与当前时间来判断 Token 是否有效
   * 添加 2 秒缓冲时间，避免边界情况
   * 
   * @param prefix Token 前缀（用于 localStorage 键名）
   * @returns boolean 如果 Token 有效则返回 true，否则返回 false
   */
  private isTokenValid(prefix: string): boolean {
      const expiration = localStorage.getItem(prefix + '_expiration');
      return expiration !== null && Number(expiration) > (Date.now() + 2000);
    }

  /**
   * 清除所有 Token
   * 
   * 清除 localStorage 中存储的所有 Token 和过期时间
   * 并通知未认证状态
   */
  private clearTokens(): void {
    localStorage.removeItem('jwt_token');
    localStorage.removeItem('jwt_token_expiration');
    localStorage.removeItem('refresh_token');
    localStorage.removeItem('refresh_token_expiration');
    this.notifyUnauthenticated();
  }

  /**
   * 通知未认证状态
   * 
   * 向 NgRx Store 发送未认证 action
   * 用于更新应用的认证状态
   */
  private notifyUnauthenticated(): void {
    this.store.dispatch(actionAuthUnauthenticated());
  }

  /**
   * 通知认证状态
   * 
   * 向 NgRx Store 发送认证 action
   * 用于更新应用的认证状态和用户信息
   * 
   * @param authPayload 认证载荷，包含用户信息
   */
  private notifyAuthenticated(authPayload: AuthPayload): void {
    this.store.dispatch(actionAuthAuthenticated({ payload: authPayload }));
  }

  /**
   * 通知用户加载状态
   * 
   * 用于更新 NgRx 状态中的用户加载状态
   * 在异步加载用户信息时使用
   * 
   * @param isUserLoaded 是否已加载用户
   */
  private notifyUserLoaded(isUserLoaded: boolean): void {
    this.store.dispatch(actionAuthLoadUser({ isUserLoaded }));
  }





  //////////////////////////////////////////////////////////////

  /**
   * 获取当前用户信息
   * 
   * 从 JWT Token 中解析用户信息
   * 使用 jwt-decode 库解码 Token 载荷
   * 
   * @returns AuthUser | null 用户信息对象，如果无法解析则返回 null
   */
  public getCurrentUser(): AuthUser | null {
    const token = this.getJwtToken();
    if (!token) {
      return null;
    }

    try {
      const tokenData = jwtDecode(token) as any;
      
      const user: AuthUser = {
        id: tokenData.sub || '',
        username: tokenData.username || '',
        email: tokenData.email,
        firstName: tokenData.firstName,
        lastName: tokenData.lastName,
        avatar: tokenData.avatar
      };
      
      return user;
    } catch (e) {
      return null;
    }
  }

  /**
   * 检查用户是否已认证
   * 
   * 综合检查 JWT Token 有效性和用户信息存在性
   * 
   * @returns boolean 如果用户已认证则返回 true，否则返回 false
   */
  public isAuthenticated(): boolean {
    return this.isJwtTokenValid() && this.getCurrentUser() !== null;
  }

  // ========== 私有方法 ==========



  /**
   * 设置 Token
   * 
   * 批量设置 JWT Token 和 Refresh Token
   * 
   * @param jwtToken JWT Token
   * @param refreshToken 刷新 Token
   */
  private setTokens(jwtToken: string, refreshToken: string): void {
    this.setToken(jwtToken, 'jwt_token');
    this.setToken(refreshToken, 'refresh_token');
  }

  /**
   * 设置单个 Token
   * 
   * 使用 jwt-decode 库解析 JWT Token，计算过期时间并存储
   * 按照原始实现逻辑：检查 iat 和 exp 字段，计算 TTL
   * 
   * @param token Token 字符串
   * @param prefix Token 前缀（用于 localStorage 键名）
   */
  private setToken(token: string, prefix: string): void {
    try {
      // 尝试解析 JWT Token
      const tokenData = jwtDecode(token) as any;
      const issuedAt = tokenData?.iat;
      const expTime = tokenData?.exp;
      
      if (issuedAt && expTime) {
        const ttl = expTime - issuedAt;
        if (ttl > 0) {
          const clientExpiration = new Date().valueOf() + ttl * 1000;
          localStorage.setItem(prefix, token);
          localStorage.setItem(prefix + '_expiration', '' + clientExpiration);
        }
      }
    } catch (e) {
      // 如果不是 JWT Token（如 Refresh Token），直接存储
      localStorage.setItem(prefix, token);
      // Refresh Token 设置较长的过期时间（24小时）
      const refreshExpiration = new Date().valueOf() + (24 * 60 * 60 * 1000);
      localStorage.setItem(prefix + '_expiration', '' + refreshExpiration);
    }
  }
}
